/*
 * Copyright (C) 2012 Igalia, S.L.
 * Copyright (C) 2014 Sony Computer Entertainment Inc.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "config.h"
#include "AcceleratedCompositingContext.h"

#if USE(ACCELERATED_COMPOSITING) && USE(ACAGI)

#include "AcagiCompositor.h"
#include "AcagiLayer.h"
#include "CairoUtilities.h"
#include "Chrome.h"
#include "ChromeClientManx.h"
#include "Frame.h"
#include "FrameView.h"
#include "GraphicsLayerAcagi.h"
#include "Page.h"
#include "PlatformContextCairo.h"
#include "Settings.h"
#include "WebViewPrivate.h"
#include <wtf/CurrentTime.h>

#define DBG_TRACE printf("@@@ %s %d\n", __PRETTY_FUNCTION__, __LINE__)
// #define DBG_TRACE

using namespace WebCore;

namespace WebKit {

AcceleratedCompositingContext::AcceleratedCompositingContext(WebViewPrivate* webViewPrivate)
    : m_webViewPrivate(webViewPrivate)
    , m_layerFlushTimer(this, &AcceleratedCompositingContext::layerFlushTimerFired)
    , m_contentsScale(1)
{
    DBG_TRACE;
}

void AcceleratedCompositingContext::initialize()
{
    DBG_TRACE;
    if (m_rootLayer)
        return;

    IntSize pageSize = m_webViewPrivate->m_viewSize;

    m_rootLayer = GraphicsLayer::create(0, this);
    m_rootLayer->setDrawsContent(false);
    m_rootLayer->setSize(pageSize);

    // The non-composited contents are a child of the root layer.
    m_nonCompositedContentLayer = GraphicsLayer::create(0, this);
    m_nonCompositedContentLayer->setDrawsContent(true);
    m_nonCompositedContentLayer->setContentsOpaque(true);
    m_nonCompositedContentLayer->setSize(pageSize);
    if (corePage(m_webViewPrivate)->settings()->acceleratedDrawingEnabled())
        m_nonCompositedContentLayer->setAcceleratesDrawing(true);

#ifndef NDEBUG
    m_rootLayer->setName("Root layer");
    m_nonCompositedContentLayer->setName("Non-composited content");
#endif

    m_rootLayer->addChild(m_nonCompositedContentLayer.get());
    m_nonCompositedContentLayer->setNeedsDisplayInRect(IntRect(IntPoint(), pageSize));

    m_commandBuffer = SigreCommandBuffer::create(m_webViewPrivate->m_pool, m_webViewPrivate->m_painter);
    m_compositor = AcagiCompositor::create(m_commandBuffer.get());
    toAcagiLayer(m_rootLayer.get())->setCompositor(m_compositor.get());
    ((GraphicsLayerAcagi*)m_nonCompositedContentLayer.get())->setActualVisibleRect(IntRect(IntPoint::zero(), pageSize));

    scheduleLayerFlush();
}

AcceleratedCompositingContext::~AcceleratedCompositingContext()
{
    stopAnyPendingLayerFlush();
}

void AcceleratedCompositingContext::stopAnyPendingLayerFlush()
{
    DBG_TRACE;
    m_layerFlushTimer.stop();
}

bool AcceleratedCompositingContext::enabled()
{
    return m_rootLayer && m_compositor && m_commandBuffer;
}

void AcceleratedCompositingContext::patchAndSubmitDrawCommands(const WebCore::IntRect& clipRect, WebCore::SigreCommandBuffer::TargetSurface* targetSurface)
{
    if (!enabled())
        return;
    m_commandBuffer->patchAndSubmitDrawCommands(clipRect, targetSurface);
}

void AcceleratedCompositingContext::compositeLayersToContext(CompositePurpose purpose)
{
    m_compositor->beginPainting();
    toAcagiLayer(m_rootLayer.get())->paint();
    m_compositor->endPainting();
}

void AcceleratedCompositingContext::clearEverywhere()
{
    DBG_TRACE;
    if (m_commandBuffer)
        m_commandBuffer->clearCommands();
}

void AcceleratedCompositingContext::setRootCompositingLayer(GraphicsLayer* graphicsLayer)
{
    DBG_TRACE;
    // Clearing everywhere when turning on or off the layer tree prevents us from flashing
    // old content before the first flush.
    clearEverywhere();

    if (!graphicsLayer) {
        stopAnyPendingLayerFlush();

        m_rootLayer = nullptr;
        m_nonCompositedContentLayer = nullptr;
        m_compositor = nullptr;
        return;
    }

    // Add the accelerated layer tree hierarchy.
    initialize();

    m_nonCompositedContentLayer->removeAllChildren();
    m_nonCompositedContentLayer->addChild(graphicsLayer);

    stopAnyPendingLayerFlush();

    scheduleLayerFlush();
}

void AcceleratedCompositingContext::setNonCompositedContentsNeedDisplay(const IntRect& rect)
{
    DBG_TRACE;
    if (!m_rootLayer)
        return;
    if (rect.isEmpty()) {
        m_rootLayer->setNeedsDisplay();
        return;
    }
    m_nonCompositedContentLayer->setNeedsDisplayInRect(rect);
    scheduleLayerFlush();
}

void AcceleratedCompositingContext::setActualVisibleRect(const WebCore::IntRect& rect)
{
    DBG_TRACE;
    ASSERT(!rect.isEmpty());
    ((GraphicsLayerAcagi*)m_nonCompositedContentLayer.get())->setActualVisibleRect(rect);
    m_contentsScale =  (float)m_webViewPrivate->m_viewSize.height() / rect.height();
    m_compositor->setContentsScale(m_contentsScale);
    scheduleLayerFlush();
}

void AcceleratedCompositingContext::resizeRootLayer(const IntSize& newSize)
{
    DBG_TRACE;
    if (!enabled())
        return;

    if (m_rootLayer->size() == newSize)
        return;

    m_rootLayer->setSize(newSize);

    // If the newSize exposes new areas of the non-composited content a setNeedsDisplay is needed
    // for those newly exposed areas.
    FloatSize oldSize = m_nonCompositedContentLayer->size();
    m_nonCompositedContentLayer->setSize(newSize);

    if (newSize.width() > oldSize.width()) {
        float height = std::min(static_cast<float>(newSize.height()), oldSize.height());
        m_nonCompositedContentLayer->setNeedsDisplayInRect(FloatRect(oldSize.width(), 0, newSize.width() - oldSize.width(), height));
    }

    if (newSize.height() > oldSize.height())
        m_nonCompositedContentLayer->setNeedsDisplayInRect(FloatRect(0, oldSize.height(), newSize.width(), newSize.height() - oldSize.height()));

    m_nonCompositedContentLayer->setNeedsDisplayInRect(IntRect(IntPoint(), newSize));
    compositeLayersToContext(ForResize);
    scheduleLayerFlush();
}

void AcceleratedCompositingContext::scheduleLayerFlush()
{
    DBG_TRACE;
    if (!enabled())
        return;

    if (!m_layerFlushTimer.isActive())
        m_layerFlushTimer.startOneShot(0);
}

bool AcceleratedCompositingContext::flushPendingLayerChanges()
{
    toAcagiLayer(m_rootLayer.get())->computeTransformsRecursive();
    m_rootLayer->flushCompositingStateForThisLayerOnly();
    m_nonCompositedContentLayer->flushCompositingStateForThisLayerOnly();
    return corePage(m_webViewPrivate)->mainFrame()->view()->flushCompositingStateIncludingSubframes();
}

void AcceleratedCompositingContext::flushAndRenderLayers()
{
    if (!enabled())
        return;

    Frame* frame = corePage(m_webViewPrivate)->mainFrame();
    if (!frame || !frame->contentRenderer() || !frame->view())
        return;
    frame->view()->updateLayoutAndStyleIfNeededRecursive();

    if (!enabled())
        return;

    if (!flushPendingLayerChanges())
        return;

    compositeLayersToContext();

    // Notify client
    m_webViewPrivate->client().didUpdateDrawing(0, 0);
}

void AcceleratedCompositingContext::layerFlushTimerFired(Timer<AcceleratedCompositingContext>*)
{
    layerFlushTimerFired();

    // m_rootLayer can be destroyed in layerFlushTimerFired() thus we need to check at this point.
    if (!enabled())
        return;

    if (toAcagiLayer(m_rootLayer.get())->descendantsOrSelfHaveRunningAnimations() && !m_layerFlushTimer.isActive())
        m_layerFlushTimer.startOneShot(1.0 / 60.0);
}

void AcceleratedCompositingContext::layerFlushTimerFired()
{
    flushAndRenderLayers();
}

void AcceleratedCompositingContext::notifyAnimationStarted(const GraphicsLayer*, double time)
{
    DBG_TRACE;
}

void AcceleratedCompositingContext::notifyFlushRequired(const GraphicsLayer*)
{
    DBG_TRACE;
}

void AcceleratedCompositingContext::paintContents(const GraphicsLayer* graphicsLayer, GraphicsContext& context, GraphicsLayerPaintingPhase, const IntRect& rectToPaint)
{
    if (graphicsLayer != m_nonCompositedContentLayer)
        return;

    WebCore::FrameView* view = corePage(m_webViewPrivate)->mainFrame()->view();

    bool paintsEntireContents = view->paintsEntireContents();
    bool scrollbarsSuppressed = view->scrollbarsSuppressed();
    view->setPaintsEntireContents(true);
    view->setScrollbarsSuppressed(true, false);

    context.save();
    context.clip(rectToPaint);
    view->paint(&context, rectToPaint);
    context.restore();

    view->setPaintsEntireContents(paintsEntireContents);
    view->setScrollbarsSuppressed(scrollbarsSuppressed, false);
}

} // namespace WebKit

#endif // USE(ACCELERATED_COMPOSITING) && USE(ACAGI)
